﻿using System;
using System.IO;
using FLib;
using LibraryGuard.Interfaces;
using LibraryGuard.Interfaces.Configuration;

namespace LibraryGuard.Utilities
{
	/// <summary>
	/// Class responsible for managing continuous mode.
	/// </summary>
	public sealed class ContinuousMode
		: IDisposable
	{
		#region Logger
		private static NLog.Logger Logger = NLog.LogManager.GetLogger("ContinuousMode");
		#endregion

		#region Config
		private const int BufferSize = 64 * 1024;
		private const NotifyFilters NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.Attributes;
		#endregion

		#region Private fields
		private readonly IConfiguration Config = null;
		private readonly IGuard[] Guards = null;
		private readonly FileSystemWatcher[] Watchers = null;
		#endregion

		#region Constructors
		/// <summary>
		/// Initializes object.
		/// </summary>
		/// <param name="config"></param>
		/// <param name="guards"></param>
		public ContinuousMode(IConfiguration config, IGuard[] guards)
		{
			this.Config = config;
			this.Guards = guards;
			this.Watchers = new FileSystemWatcher[config.Directories.Count];
		}
		#endregion

		#region Methods
		/// <summary>
		/// Runs.
		/// </summary>
		public void Run()
		{
			for (int i = 0; i < this.Config.Directories.Count; i++)
			{ 
				var watcher = this.Watchers[i] = new FileSystemWatcher(this.Config.Directories[i].FullName);

				watcher.NotifyFilter = NotifyFilter;
				watcher.IncludeSubdirectories = true;
				watcher.InternalBufferSize = BufferSize;

				watcher.Created += this.FileSystemEvent;
				watcher.Changed += this.FileSystemEvent;
				watcher.Deleted += this.FileSystemEvent;
				watcher.Renamed += this.FileSystemEvent;

				watcher.EnableRaisingEvents = true;

				Logger.Trace("Watcher for directory '{0}' enabled", watcher.Path);
			}
		}

		/// <summary>
		/// Disables raising events.
		/// </summary>
		public void Disable()
		{
			Validate.Debug(() => this.Watchers, v => v.ForAll<FileSystemWatcher>().NotNull());
			foreach (var watcher in this.Watchers)
			{
				watcher.EnableRaisingEvents = false;
			}
		}

		/// <summary>
		/// Enables raising events.
		/// </summary>
		public void Enable()
		{
			Validate.Debug(() => this.Watchers, v => v.ForAll<FileSystemWatcher>().NotNull());
			foreach (var watcher in this.Watchers)
			{
				watcher.EnableRaisingEvents = true;
			}
		}
		#endregion

		#region Private Methods
		private void FileSystemEvent(object sender, FileSystemEventArgs e)
		{
			Logger.Debug("Event '{0}' fired, path: {1}", e.ChangeType, e.FullPath);
			foreach (var guard in this.Guards)
			{
				if (guard.OnlySpecificExtensions)
				{
					var ext = Path.GetExtension(e.Name);
					if (ext.Length > 0)
						ext = ext.Remove(0, 1);
					if (!this.Config.Extensions.Contains(ext))
						continue;
				}
				guard.FileSystemEvent(e, ((FileSystemWatcher)sender).Path);
			}
		}
		#endregion

		#region IDisposable Members
		/// <summary>
		/// Releases all watchers.
		/// </summary>
		public void Dispose()
		{
			foreach (var watcher in this.Watchers)
			{
				watcher.Created -= this.FileSystemEvent;
				watcher.Changed -= this.FileSystemEvent;
				watcher.Deleted -= this.FileSystemEvent;
				watcher.Renamed -= this.FileSystemEvent;

				watcher.Dispose();
			}
		}
		#endregion
	}
}
